﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Red.Core.UndoRedo
{

    /// <summary>
    /// Undo/redo list for game data actions
    /// </summary>
    public class ActionHistory
    {
        //-----------------------------------

        public delegate void HistoryEventHandler(object sender, EventArgs e);
        public event HistoryEventHandler OnActionExecuted;
        public event HistoryEventHandler OnActionFailed;
        public event HistoryEventHandler OnActionUndone;
        public event HistoryEventHandler OnActionRedone;
        public event HistoryEventHandler OnActionHistoryCleared;

        //-----------------------------------

        /// <summary>
        /// Create the undo/redo list
        /// </summary>
        public ActionHistory()
        {
            _UndoList = new List<IAction>();
            _RedoList = new List<IAction>();
        }

        /// <summary>
        /// Do we have anything to undo?
        /// </summary>
        public bool HasUndo
        {
            get
            {
                return _UndoList.Count != 0;
            }
        }

        /// <summary>
        /// Get name of the action that on the top of the undo list
        /// </summary>
        public string UndoActionDisplayName
        {
            get
            {
                if ( _UndoList.Count == 0 )
                    return "No Undo";

                return _UndoList[_UndoList.Count - 1].GetDisplayName();
            }
        }

        /// <summary>
        /// Get name of the action that on the top of the redo list
        /// </summary>
        public string RedoActionDisplayName
        {
            get
            {
                if ( _RedoList.Count == 0 )
                    return "No Redo";

                return _RedoList[_RedoList.Count - 1].GetDisplayName();
            }
        }

        /// <summary>
        /// Do we have anything to redo?
        /// </summary>
        public bool HasRedo
        {
            get
            {
                return _RedoList.Count != 0;
            }
        }

        /// <summary>
        /// Remove all actions from undo/redo history
        /// </summary>
        public void ClearHistory()
        {
            _UndoList.Clear();
            _RedoList.Clear();

            if (OnActionHistoryCleared != null )
                OnActionHistoryCleared(this, new EventArgs() );
        }

        /// <summary>
        /// Execute an action - if the action was successful it's added to the UndoList
        /// </summary>
        /// <param name="action">Action to execute</param>
        /// <param name="errorString">Error string - returned by the action in case of any errors</param>
        /// <returns>True if the action was executed</returns>
        public bool Execute(IAction action, ref string errorString)
        {
            // empty action
            if (action == null)
                return false;

            // execute action
            if (!action.Do(this, ref errorString))
            {
                // notify listeners
                if (OnActionFailed != null)
                    OnActionFailed(this, new EventArgs());

                return false; // action failed to execute
            }

            // we changed the action flow so we need to cleanup the redo list from any actions that are there
            // we wont be able to redo them any more
            _RedoList.Clear();

            // put the just executed action in the undo list
            _UndoList.Add(action);

            // notify listeners
            if (OnActionExecuted != null)
                OnActionExecuted(this, new EventArgs());

            return true;
        }

        /// <summary>
        /// Undo last action. If successful then the action is moved to the redo list.
        /// </summary>
        /// <param name="errorString">If action fails here's how it will return you the error message</param>
        /// <returns>True if undoing was successful. Will return false if the list of actions to undo is empty.</returns>
        public bool Undo(ref string errorString)
        {
            // no actions in the undo list
            if (_UndoList.Count == 0)
                return false;

            // undo the last action
            var lastAction = _UndoList[ _UndoList.Count-1 ];
            if (!lastAction.Undo(this, ref errorString))
                return false;

            // move action from the undo to redo list
            _UndoList.Remove(lastAction);
            _RedoList.Add(lastAction);

            // notify listeners
            if (OnActionUndone != null)
                OnActionUndone(this, new EventArgs());

            // action changed
            return true;
        }

        /// <summary>
        /// Redo action that was just undone. If successful then the action is moved to the undo list.
        /// </summary>
        /// <param name="errorString">If action fails here's how it will return you the error message</param>
        /// <returns>True if redoing was successful. Will return false if the list of actions to redo is empty.</returns>
        public bool Redo(ref string errorString)
        {
            // no actions in the undo list
            if (_RedoList.Count == 0)
                return false;

            // redo the last action
            var lastAction = _RedoList[_RedoList.Count - 1];
            if (!lastAction.Do(this, ref errorString))
                return false;

            // move action from the undo to redo list
            _RedoList.Remove(lastAction);
            _UndoList.Add(lastAction);

            // notify listeners
            if (OnActionRedone != null)
                OnActionRedone(this, new EventArgs());

            // action changed
            return true;
        }

        /// <summary>
        /// List of already executed actions that can be undone
        /// </summary>
        private List<IAction> _UndoList;

        /// <summary>
        /// List of reverted actions that may be reapplied
        /// </summary>
        private List<IAction> _RedoList;
    }

}
